package com.dirven.game.sprite;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.dirven.game.Coord;
import com.dirven.game.DirVenGame;
import com.dirven.game.Grid;

public abstract class GridSprite extends com.badlogic.gdx.graphics.g2d.Sprite {

	public final static int TYPE_NPC = 0,
							TYPE_PLAYER = 1,
							TYPE_OBJECT = 2,
							TYPE_POWERUP = 3,
							TYPE_REFLECT = 4;
	
	private int type;
	
	public final static int DIRECTION_NORTH = 0,
							DIRECTION_SOUTH = 1,
							DIRECTION_EAST = 2,
							DIRECTION_WEST = 3;
	
	protected int direction;			// The direction of movement
	private int movementUnits = 1;	// The number of cells the GridSprite moves.
	
	private String folder;			// The folder containing the sprite image.
	
	// Array of coordinates
	private LinkedList<Coord> gridCoords;		// The sprite's chain of coordinates.
	
	private ArrayList<JointSprite> joints;
	
	private boolean visible = true;	// The sprite's visibility
	private boolean dead = false;	// The sprites's death state.
	
	// If the sprite is moving to a destination, destinationCoordinate will be the coordinate of the destination,
	// otherwise it is null
	private Coord destinationCoordinate = null;
	
	// If the sprite is moving to a destination, this is set to true, otherwise false.
	private boolean isMovingToDestinationCoordinate = false;
	
	private boolean isMovementDisabled = false;
	
	private boolean isDespawning = false;

	public boolean isReflecting = false;
	
	/**
	 * GridSprite constructor
	 * 
	 * @param x
	 * @param y
	 * @param type
	 * @param folder
	 * @param headImage
	 */
	public GridSprite(int x, int y, int type, String folder, String headImage) {
		super(new Texture(Gdx.files.internal("data/images/sprites/" + folder + "/" + headImage)));
		//super.setSize((float)Grid.cellWidth, (float)Grid.cellHeight);
		
		Coord pixelCoords = Grid.getPixelPositionOfCell(x, y);
		super.setPosition(pixelCoords.x, pixelCoords.y);
		pixelCoords = null;
		
		this.type = type;
		this.folder = folder;
		
		gridCoords = new LinkedList<Coord>();
		gridCoords.add(new Coord(x, y));
		
		joints = new ArrayList<JointSprite>(9);
	}
	
	public void addGridCoord(int x, int y) {
		gridCoords.add(new Coord(x, y));
	}
	
	/**
	 * Set the sprite's image
	 * 
	 * @param image
	 */
	public void setHeadImage(String image) {
		super.setTexture(new Texture(Gdx.files.internal("data/images/sprites/" + folder + "/" + image)));
		super.setSize((float)Grid.cellWidth, (float)Grid.cellHeight);
	}
	
	/**
	 * Get the coordinate of the head of the chain.
	 * 
	 * @return
	 */
	public Coord getHeadPosition() {
		return gridCoords.get(0);
	}
	
	/**
	 * Get an array of coordinates in the sprite's chain.
	 * 
	 * @return
	 */
	public LinkedList<Coord> getCoords() {
		return gridCoords;
	}
	
	
	/**
	 * Called every time the screen updates
	 * 
	 */
	public void update() {
		if(isDead() == false && isVisible() == true && isMovementDisabled == false) {
			movementAI();
		}
		
		updateJointSprites();
	}
	
	/**
	 * AI movement
	 */
	public abstract void movementAI();
	
	/**
	 * Set the direction of movement.
	 * 
	 * @param newDirection
	 */
	public void setDirection(int newDirection) {
		
		if(direction == DIRECTION_SOUTH && newDirection == DIRECTION_NORTH) {
			return;
		}
		if(direction == DIRECTION_NORTH && newDirection == DIRECTION_SOUTH) {
			return;
		}
		if(direction == DIRECTION_EAST && newDirection == DIRECTION_WEST) {
			return;
		}
		if(direction == DIRECTION_WEST && newDirection == DIRECTION_EAST) {
			return;
		}
		direction = newDirection;
		
		switch(direction) {
			case DIRECTION_NORTH: break;
			case DIRECTION_SOUTH: super.setRotation(180); break;
			case DIRECTION_WEST: super.setRotation(90); break;
			case DIRECTION_EAST: super.setRotation(-90); break;
		}
	}
	
	/**
	 * Move the grid sprite 1 unit in the direction it is facing.
	 * 
	 * @param append - If set to true, then the last coordinate in the sprite chain will not be removed.
	 * 				   If set to false, then the last coordinate in the sprite chain will be removed.
	 */
	public void move(boolean append) {
		int newX = (int)gridCoords.getFirst().x,
			newY = (int)gridCoords.getFirst().y;
			
		// if movement is disabled, don't move
		if(isMovementDisabled) {
			return;
		}
		
		// If current task is to move to a specified coordinate, continue moving towards it
		// until it reaches it.
		if(isMovingToDestinationCoordinate) {
			moveToCoordinate(destinationCoordinate);
		}
		
		
		switch(getDirection()) {
			case DIRECTION_NORTH: newY += movementUnits; break;
			case DIRECTION_SOUTH: newY -= movementUnits; break;
			case DIRECTION_EAST: newX += movementUnits; break;
			case DIRECTION_WEST: newX -= movementUnits; break;
		}
		
		
		Coord newHeadCoord = new Coord(newX, newY);
		
		// If the head of the grid sprite collides with anything then...
		if(Grid.collide(newHeadCoord)) {
			onCollide();
			
			// Garbage collection
			newHeadCoord = null;
			return;
		}
		
		gridCoords.addFirst(newHeadCoord);
		
		// If not appending, remove the last coordinate.
		if(append == false) {
			gridCoords.removeLast();
		}
		
		// Garbage collection
		newHeadCoord = null;
	}
	
	/**
	 * Check if the next head coordinate is not the current head coordinate. 
	 * (In plain English, it's not going backwards to collide with itself)
	 * @return
	 */
	public boolean nextCoordinateNotHead(int direction) {
		
		int newX = (int)getHeadPosition().x,
			newY = (int)getHeadPosition().y;
			
		switch(direction) {
			case DIRECTION_NORTH: newY += movementUnits; break;
			case DIRECTION_SOUTH: newY -= movementUnits; break;
			case DIRECTION_EAST: newX += movementUnits; break;
			case DIRECTION_WEST: newX -= movementUnits; break;
		}
		
		Coord newHeadCoord = new Coord(newX, newY);
		Coord currentHeadCoord = getHeadPosition();
		
		// If the current head coordinate is the same as the next one in the given direction,
		// then it is going backwards and will collide with itself.
		if(currentHeadCoord.equals(newHeadCoord)) {
			return true;
		} else {
			return false;
		}
		
	}
	
	/**
	 * Move the 
	 * @param direction
	 */
	public void move(int newDirection, boolean append) {
		setDirection(newDirection);
		move(append);
	}

	/**
	 * Get the type of this gridsprite.
	 * 
	 * @return
	 */
	public int getType() {
		return type;
	}
	
	public int getChainLength() {
		return gridCoords.size();
	}
	
	/**
	 * Executes when the sprite collides
	 */
	public abstract void onCollide();
	
	public abstract void onKilled();

	/**
	 * @return the visible
	 */
	public boolean isVisible() {
		return visible;
	}

	/**
	 * @param visible the visible to set
	 */
	public void setVisible(boolean visible) {
		this.visible = visible;
	}

	/**
	 * @return the dead
	 */
	public boolean isDead() {
		return dead;
	}

	/**
	 * @param dead the dead to set
	 */
	public void setDead(boolean dead) {
		this.dead = dead;
	}
	
	public void destroy() {
		
		// Dispose of texture
		if(super.getTexture() != null) {
			super.getTexture().dispose();
		}
		
		try {
			super.finalize();
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void moveToCoordinate(int x, int y) {
		moveToCoordinate( new Coord((float)x, (float)y));
	}
	
	public void moveToCoordinate(Coord coord) {
		
		if(coord == null) {
			return;
		}
		
		if(Grid.collide(coord)) {
			return;
		}
		
		Coord currentPos = this.getHeadPosition();
		
		destinationCoordinate = coord;
		
		// if at the destination already, then stop moving to it.
		if(coord.equals(currentPos)) {
			destinationCoordinate = null;
			isMovingToDestinationCoordinate = false;
			
			// Garbage collection
			currentPos = null;
			
			return;
		}
		
		// If aligned on x axis
		if(currentPos.x == coord.x) {
			
			// current y position is above destination y position, move down
			if(currentPos.y > coord.y) {
				setDirection(DIRECTION_SOUTH);
			} else if(currentPos.y < coord.y) {
				setDirection(DIRECTION_NORTH);
			}
			
		// If aligned on y axis
		} else if(currentPos.y == coord.y) {
			
			// current x position is further east(right) than destination y position, move left
			if(currentPos.x > coord.x) {
				setDirection(DIRECTION_WEST);
			} else if(currentPos.x < coord.x) {
				setDirection(DIRECTION_EAST);
			}
			
		// if sprite is heading up or down and the destination y coordinaet isn't the same go either up or down.
		} else if(currentPos.y != coord.y /*&& (getDirection() == DIRECTION_NORTH || getDirection() == DIRECTION_SOUTH)*/) {
			//Gdx.app.log("DIRVEN", "attempting to move NORTH/SOUTH");
			// If destination coordinate is BELOW current coordinate
			if(currentPos.y > coord.y) {
				//Gdx.app.log("DIRVEN", "attempting to move SOUTH");
				// Check if going SOUTH would work
				
				// If going SOUTH will NOT cause it to collide with itself
				if( nextCoordinateNotHead(DIRECTION_SOUTH)) {
					setDirection(DIRECTION_SOUTH);
					//Gdx.app.log("DIRVEN", "going to move SOUTH");
				// If going SOUTH will cause it to collide with itself (going backwards)
				} else {
					// Move left or right depending on where the destination is in terms of x.
					if(currentPos.x > coord.x) {
						setDirection(DIRECTION_WEST);
						//Gdx.app.log("DIRVEN", "SOUTH not possible, going to move WEST");
					} else if(currentPos.x < coord.x) {
						setDirection(DIRECTION_EAST);
						//Gdx.app.log("DIRVEN", "SOUTH not possible, going to move EAST");
					}
				}
				
			// If destination coordinate is ABOVE current coordinate
			} else if(currentPos.y < coord.y) {
				setDirection(DIRECTION_NORTH);
				//Gdx.app.log("DIRVEN", "attempting to move NORTH");
				// Check if going NORTH would work
				
				// If going NORTH will NOT cause it to collide with itself
				if( nextCoordinateNotHead(DIRECTION_NORTH)) {
					setDirection(DIRECTION_NORTH);
					//Gdx.app.log("DIRVEN", "going to move NORTH");
					
				// If going NORTH will cause it to collide with itself (going backwards)
				} else {
					// Move left or right depending on where the destination is in terms of x.
					if(currentPos.x > coord.x) {
						setDirection(DIRECTION_WEST);
						//Gdx.app.log("DIRVEN", "NORTH not possible, going to move WEST");
					} else if(currentPos.x < coord.x) {
						setDirection(DIRECTION_EAST);
						//Gdx.app.log("DIRVEN", "NORTH not possible, going to move EAST");
					}
				}
				
			}
		} else if(currentPos.x != coord.x /*&& (getDirection() == DIRECTION_EAST || getDirection() == DIRECTION_WEST)*/) {
			//Gdx.app.log("DIRVEN", "attempting to move EAST/WEST");
			
			if(currentPos.x > coord.x) {
				
				// Check if going WEST would work
				
				// If going WEST will NOT cause it to collide with itself
				if( nextCoordinateNotHead(DIRECTION_WEST)) {
					setDirection(DIRECTION_WEST);
					
				// If going WEST will cause it to collide with itself (going backwards)
				} else {
					// Move up or down depending on where the destination is in terms of y.
					if(currentPos.y > coord.y) {
						setDirection(DIRECTION_SOUTH);
					} else if(currentPos.y < coord.y) {
						setDirection(DIRECTION_NORTH);
					}
				}
			} else if(currentPos.x < coord.x) {
				setDirection(DIRECTION_EAST);
				
				// Check if going WEST would work
				
				// If going EAST will NOT cause it to collide with itself
				if( nextCoordinateNotHead(DIRECTION_EAST)) {
					setDirection(DIRECTION_EAST);
					
				// If going EAST will cause it to collide with itself (going backwards)
				} else {
					// Move up or down depending on where the destination is in terms of y.
					if(currentPos.y > coord.y) {
						setDirection(DIRECTION_SOUTH);
					} else if(currentPos.y < coord.y) {
						setDirection(DIRECTION_NORTH);
					}
				}
			}
		}
		
		isMovingToDestinationCoordinate = true;
		
		// Garbage colelction
		currentPos = null;
	}

	/**
	 * @return the isMovementDisabled
	 */
	public boolean isMovementDisabled() {
		return isMovementDisabled;
	}

	/**
	 * @param isMovementDisabled the isMovementDisabled to set
	 */
	public void setMovementDisabled(boolean isMovementDisabled) {
		this.isMovementDisabled = isMovementDisabled;
	}

	/**
	 * @return the direction
	 */
	public int getDirection() {
		return direction;
	}
	
	public void reverseDirection() {
		
		//Gdx.app.log("DIRVEN", "OLD HEAD ("+getHeadPosition().x+","+getHeadPosition().y+")");
		Collections.reverse(gridCoords);
		
		if(direction == DIRECTION_NORTH) {
			direction = DIRECTION_SOUTH;
		} else if(direction == DIRECTION_SOUTH) {
			direction = DIRECTION_NORTH;
		} else if(direction == DIRECTION_EAST) {
			direction = DIRECTION_WEST;
		} else if(direction == DIRECTION_WEST) {
			direction = DIRECTION_EAST;
		}
		//Gdx.app.log("DIRVEN", "NEW HEAD ("+getHeadPosition().x+","+getHeadPosition().y+")");
		
	}
	
	public void despawn() {
		setDead(true);
		isDespawning = true;
	}

	public void onReflect(Laser laser) {
		// TODO Auto-generated method stub
		
	}
	
	/**
	 * Adds a sprite that is attatched to this sprite.
	 * @param jointSprite
	 */
	public void addJointSprite(Sprite jointSprite, float xOffset, float yOffset) {
		joints.add(new JointSprite(jointSprite, xOffset, yOffset));
	}
	
	/**
	 * Adds a sprite that is attatched to this sprite.
	 * @param jointSprite
	 */
	public void addJointSprite(Sprite jointSprite, int jointLocation) {
		joints.add(new JointSprite(jointSprite, this, jointLocation));
	}
	
	private void updateJointSprites() {
		for(JointSprite js : joints) {
			Sprite joint = js.joint;
			Coord head = this.getHeadPosition();
			joint.setPosition(head.x*Grid.cellWidth + js.xOffset, head.y*Grid.cellHeight + js.yOffset);
			joint.update();
			
			joint = null;
			head = null;
		}
	}
	
	public void draw(SpriteBatch batch) {
		super.draw(batch);
		
		for(JointSprite js : joints) {
			js.joint.draw(batch);
		}
	}
}
